/*
 * Request.java
 *
 * Brazil project web application toolkit,
 * export version: 2.3 
 * Copyright (c) 1998-2009 Sun Microsystems, Inc.
 *
 * Sun Public License Notice
 *
 * The contents of this file are subject to the Sun Public License Version 
 * 1.0 (the "License"). You may not use this file except in compliance with 
 * the License. A copy of the License is included as the file "license.terms",
 * and also available at http://www.sun.com/
 * 
 * The Original Code is from:
 *    Brazil project web application toolkit release 2.3.
 * The Initial Developer of the Original Code is: suhler.
 * Portions created by suhler are Copyright (C) Sun Microsystems, Inc.
 * All Rights Reserved.
 * 
 * Contributor(s): cstevens, drach, rinaldo, suhler.
 *
 * Version:  2.18
 * Created by suhler on 98/09/14
 * Last modified by suhler on 09/09/28 09:04:58
 *
 * Version Histories:
 *
 * 2.18 09/09/28-09:04:58 (suhler)
 *   add bufSize option for in/upt/output buffers
 *
 * 2.17 09/09/04-14:10:29 (suhler)
 *   0 length responses no longer get turned into "nocontent".  This is
 *   in better compliance with the spec, but incompatible with existing
 *   behavior.
 *
 * 2.16 09/09/01-10:16:02 (suhler)
 *   add support for chunked encoding for postData
 *
 * 2.15 09/04/03-13:25:08 (suhler)
 *   add public getProtocol method
 *   .
 *
 * 2.14 09/03/27-09:32:22 (suhler)
 *   add better http/vs https support
 *
 * 2.13 08/07/29-15:33:14 (suhler)
 *   be more generous when considering post data to be x-www-form-urlencoded
 *
 * 2.12 07/06/21-15:36:36 (suhler)
 *   add canChunk()
 *
 * 2.11 07/03/07-09:15:59 (suhler)
 *   added log messages for post failures
 *
 * 2.10 06/11/13-14:17:10 (suhler)
 *   tweak error message
 *
 * 2.9 06/04/28-16:16:26 (suhler)
 *   don't send content for "204" results
 *   .
 *
 * 2.8 06/01/17-09:42:21 (suhler)
 *   doc fixes
 *
 * 2.7 05/12/08-13:03:19 (suhler)
 *   protect against DOS from bogus http requests
 *
 * 2.6 04/11/30-15:19:41 (suhler)
 *   fixed sccs version string
 *
 * 2.5 04/11/03-08:35:16 (suhler)
 *   set "url.orig" in request
 *
 * 2.4 03/08/01-16:17:47 (suhler)
 *   fixes for javadoc
 *
 * 2.3 03/05/12-16:27:37 (suhler)
 *   Merged changes between child workspace "/home/suhler/brazil/naws" and
 *   parent workspace "/net/mack.eng/export/ws/brazil/naws".
 *
 * 1.71.1.1 03/05/12-16:24:16 (suhler)
 *   added a "serverProtocol" variable to allow handlers to change the
 *   protocol (e.g. http) the server claims to be using on a per-request basis.
 *   If "serverProtocol" is unchanged (the default), then the Server.protocol
 *   is used instead.
 *   This is useful when fronting Brazil servers with ssl gateways; the protocol
 *   can be changed from "http" to "https" to allow redirects to work
 *   properly
 *
 * 2.2 02/10/18-15:04:49 (drach)
 *   Add shared props after Request.props rather than before server.props
 *
 * 2.1 02/10/01-16:34:52 (suhler)
 *   version change
 *
 * 1.71 02/08/21-18:25:46 (suhler)
 *   doc update
 *
 * 1.70 02/06/27-18:38:47 (suhler)
 *   don't override content type if its already been set
 *
 * 1.69 02/05/07-07:04:18 (drach)
 *   Add no arg constructor.
 *
 * 1.68 02/04/23-14:02:36 (suhler)
 *   bug fix exposed by tests
 *
 * 1.67 02/04/18-11:17:23 (suhler)
 *   add processing for "HEAD" requests in sendResponse(),  This might not
 *   cover all the cases yet
 *
 * 1.66 02/02/07-14:06:10 (suhler)
 *   sendResponse() now does the "right think" for HEAD requests
 *
 * 1.65 01/09/13-09:26:22 (suhler)
 *   remove uneeded import
 *
 * 1.64 01/08/22-16:23:07 (drach)
 *   Change comments regarding shared properties.
 *
 * 1.63 01/08/21-10:59:16 (suhler)
 *   add check for maximum request length
 *
 * 1.62 01/08/20-17:32:21 (suhler)
 *   change readFully() to read()
 *
 * 1.61 01/08/13-09:09:29 (drach)
 *   Change server.props back to Properties object.
 *
 * 1.60 01/08/07-14:19:30 (drach)
 *   Move PropertiesList debug initialization to earliest possible point.
 *
 * 1.59 01/08/03-15:29:31 (drach)
 *   Remove SharedProps and add PropertiesList
 *
 * 1.58 01/07/10-10:37:58 (drach)
 *   Account for semantic change for equals() in Dictionary objects.
 *
 * 1.57 01/06/05-22:14:06 (drach)
 *   Reduce access control for brazil.servlet package
 *
 * 1.56 01/05/30-08:33:43 (drach)
 *   Reduce access control on selected fields and methods.
 *
 * 1.55 01/05/24-19:53:51 (drach)
 *   Count all bytes written.
 *
 * 1.54 01/03/12-17:42:20 (cstevens)
 *   Merged changes between child workspace "/home/cstevens/ws/brazil/naws" and
 *   parent workspace "/export/ws/brazil/naws".
 *
 * 1.52.1.1 01/03/12-17:29:31 (cstevens)
 *   Change log() methods to make testing easier.
 *
 * 1.53 01/03/06-11:16:47 (suhler)
 *   chage Vector.add() to Vector.addElement() for compatibility with java 1.1
 *
 * 1.52 01/03/05-16:21:46 (cstevens)
 *   test suite:
 *   Timeout during request gave error "null 408 Timeout".  Should be
 *   "HTTP/1.1 408 Timeout"
 *
 * 1.51 01/02/20-16:29:11 (cstevens)
 *   docs
 *
 * 1.50 01/02/20-16:16:24 (cstevens)
 *   docs.
 *
 * 1.49 01/02/20-15:52:32 (cstevens)
 *   Shared Dictionaries are searched after request.props and before server.props.
 *
 * 1.48 01/02/12-17:15:02 (cstevens)
 *   Get rid of Request.RechainableProperties public inner class.
 *   Add method to Request to add an existing Properties object to a request w/o
 *   exposing a special class or requiring all users of Request to use a special
 *   class.  Works for multithreaded sharing of the same end-user provided
 *   Properties object also, which the RechainableProperties didn't do.
 *
 * 1.47 00/12/05-13:12:36 (suhler)
 *   added getStatus() and getReuseCount() methods for statistics gathering
 *   - changed semantics of bytesWritten: resets at each request
 *
 * 1.46 00/11/20-13:22:13 (suhler)
 *   doc fixes
 *
 * 1.45 00/11/15-09:50:35 (suhler)
 *   added start-of-request timestamp
 *
 * 1.44 00/10/05-09:18:48 (suhler)
 *   remove dead code
 *
 * 1.43 00/07/07-17:02:18 (suhler)
 *   remove System.out.println(s)
 *
 * 1.42 00/07/06-15:49:29 (suhler)
 *   doc update
 *
 * 1.41 00/06/29-10:47:15 (suhler)
 *   HttpOutputStream counts the # of bytes written
 *
 * 1.40 00/05/31-13:51:32 (suhler)
 *   docs
 *
 * 1.39 00/05/15-12:02:23 (suhler)
 *   remove dependency on regexp package by putting in ugly code
 *
 * 1.38 00/03/29-16:43:43 (cstevens)
 *   Request was missing documentation of basic functionality.
 *
 * 1.37 00/03/10-17:11:33 (cstevens)
 *   Eliminated DataOutputStream from Request.java.
 *   Added a rechainable Properties object to Request.java
 *
 * 1.36 99/11/16-14:35:29 (cstevens)
 *   Request.java:
 *   1. If the "Content-Length" provided in the request was malformed, the
 *   log message should display how it was malformed.
 *   2. If Request.sendError() was called, it was logging the word "null"
 *   if the user passed in null for the detail message.  Aesthetically
 *   displeasing.
 *   3. If Request.sendError() is called when part of a response has already
 *   been sent, it cannot send the error headers and error message, because
 *   that would confuse things, so it must just close the connection.  That
 *   was done right, but it wasn't bumping the Server.errorCount.  It still
 *   should do that always.
 *
 * 1.35 99/11/03-17:52:31 (cstevens)
 *   MultiHostHandler.
 *
 * 1.34 99/10/26-18:54:35 (cstevens)
 *   Eliminate public methods Server.initHandler() and Server.initObject().
 *   Get rid of public variables Request.server and Request.sock:
 *   A. In all cases, Request.server was not necessary; it was mainly used for
 *   constructing the absolute URL for a redirect, so Request.redirect() was
 *   rewritten to take an absolute or relative URL and do the right thing.
 *   B. Request.sock was changed to Request.getSock(); it is still rarely used
 *   for diagnostics and logging (e.g., ChainSawHandler).
 *   HTTP request returned ungracefully if "Content-Length" in a POST was
 *   negative or too much to allocate.
 *
 * 1.33 99/10/25-15:48:03 (cstevens)
 *   Merged changes between child workspace "/home/cstevens/ws/brazil/naws" and
 *   parent workspace "/export/ws/brazil/naws".
 *
 * 1.31.1.1 99/10/25-15:38:48 (cstevens)
 *   String.equals() can take null.
 *
 * 1.32 99/10/25-14:32:44 (suhler)
 *   missing content length
 *
 * 1.25.2.1 99/10/23-19:49:32 (rinaldo)
 *
 * 1.31 99/10/19-18:57:53 (cstevens)
 *   toString
 *
 * 1.30 99/10/14-13:14:59 (cstevens)
 *   Merged changes between child workspace "/home/cstevens/ws/brazil/naws" and
 *   parent workspace "/export/ws/brazil/naws".
 *
 * 1.28.1.3 99/10/14-13:11:04 (cstevens)
 *   @author & @version
 *
 * 1.28.1.2 99/10/14-12:54:36 (cstevens)
 *   don't display socket information twice when logging.
 *   rewrite sendResponse() to take InputStream rather than HttpInputStream.
 *   Maybe this is a bad idea.
 *
 * 1.29 99/10/11-12:36:25 (suhler)
 *   Merged changes between child workspace "/home/suhler/brazil/naws" and
 *   parent workspace "/net/mack.eng/export/ws/brazil/naws".
 *
 * 1.27.1.1 99/10/11-12:32:00 (suhler)
 *   we were getting duplicate mime headers
 *
 * 1.28.1.1 99/10/08-16:53:26 (cstevens)
 *   Handle malformed URLs that have spaces in the name.  These type of URLS are
 *   sent by RealAudio clients.  HTTP request line is actually simpler now.
 *
 * 1.28 99/10/07-13:01:25 (cstevens)
 *   javadoc lint
 *   accessor methods for setting an integer mime header.
 *
 * 1.27 99/10/04-16:05:37 (cstevens)
 *   Merged changes between child workspace "/home/cstevens/ws/brazil/naws" and
 *   parent workspace "/export/ws/brazil/naws".
 *
 * 1.26 99/10/01-11:41:52 (suhler)
 *   Merged changes between child workspace "/home/suhler/brazil/naws" and
 *   parent workspace "/net/mack.eng/export/ws/brazil/naws".
 *
 * 1.24.1.2 99/10/01-11:40:05 (suhler)
 *   no query string == "" not null
 *
 * 1.25.1.1 99/10/01-11:27:04 (cstevens)
 *   Change logging to show prefix of Handler generating the log message.
 *
 * 1.25 99/09/30-10:45:43 (cstevens)
 *   Multiple instances of "Server" and "Date" in response: fixed.
 *
 * 1.24.1.1 99/09/22-16:02:49 (suhler)
 *   commented out debugging code
 *
 * 1.24 99/09/15-14:51:24 (cstevens)
 *   import *;
 *
 * 1.23 99/09/15-14:41:24 (cstevens)
 *   Rewritign http server to make it easier to proxy requests.
 *
 * 1.22 99/09/15-13:32:29 (cstevens)
 *   Rewriting http server to allow easier proxying.
 *
 * 1.21 99/08/17-14:50:33 (suhler)
 *   Added convenience method for sending binary data as a response
 *
 * 1.20 99/07/30-10:52:14 (suhler)
 *   always flush after emitting headers
 *   This is used by the tailHandler as a "signal" that all the headers
 *   have been output
 *
 * 1.19 99/06/29-14:34:16 (suhler)
 *   Added public serverUrl method to return a url suitable for redirects
 *   back to the same server
 *   .
 *
 * 1.18 99/06/28-10:53:39 (suhler)
 *   added getServer() method
 *
 * 1.17 99/04/08-09:52:21 (suhler)
 *   Make sure "responseCode" is correct after request is complete
 *
 * 1.16 99/04/07-13:03:18 (suhler)
 *   make responseCode public (why not?)
 *   .
 *
 * 1.15 99/04/01-09:06:02 (suhler)
 *   - Allow PUT requests
 *   - Added 2 more http return codes to support PUT
 *
 * 1.14 99/03/30-09:42:03 (suhler)
 *   Merged changes between child workspace "/home/suhler/brazil/naws" and
 *   parent workspace "/net/smartcard.eng/export/ws/brazil/naws".
 *
 * 1.11.1.1 99/03/30-09:26:27 (suhler)
 *   - documentation updates
 *   - fixed non-initialized bug in "connection" headers and request type
 *
 * 1.13 99/03/23-17:21:43 (cstevens)
 *   resolve wildcard imports.
 *
 * 1.12 99/03/23-15:11:28 (cstevens)
 *   Issuing an illegal HTTP request to brazil server resulted in HTTP error
 *   response: "null 400 Bad Request" instead of "HTTP/1.0 400 Bad Request"
 *
 * 1.11 99/03/09-11:27:23 (suhler)
 *   Merged changes between child workspace "/home/suhler/brazil/naws" and
 *   parent workspace "/net/smartcard.eng/export/ws/brazil/naws".
 *
 * 1.9.1.1 99/03/09-11:22:36 (suhler)
 *   handle 100 continue
 *
 * 1.10 99/02/17-17:27:41 (cstevens)
 *   Merged changes between child workspace "/home/cstevens/ws/brazil/naws" and
 *   parent workspace "/net/smartcard.eng/export/ws/brazil/naws".
 *
 * 1.6.1.1 99/02/17-17:17:26 (cstevens)
 *   Brazil server has never correctly handled "%XX" in the url or query data.  It
 *   treated it as a decimal number.
 *
 * 1.9 99/02/10-10:36:01 (suhler)
 *   - changed mime processing to allow un-collapsing of headers
 *   - made a couple of private variables public, for the proxyHandler
 *
 * 1.8 99/02/03-14:12:34 (suhler)
 *   removed superplusous output
 *
 * 1.7 99/01/29-11:48:44 (suhler)
 *   redo connection headers
 *
 * 1.6 98/11/18-13:42:46 (suhler)
 *   The already removed keep-alive stuff creapt back in some how
 *
 * 1.5 98/11/16-16:04:03 (suhler)
 *   Added flag to turn off keep-alives for http/1.0
 *   ./
 *   .
 *
 * 1.4 98/10/27-14:26:30 (suhler)
 *   Added constructor to getQueryData to take pre-existing hash table
 *
 * 1.3 98/10/13-12:05:27 (suhler)
 *   typo
 *
 * 1.2 98/09/21-14:51:36 (suhler)
 *   changed the package names
 *
 * 1.2 98/09/14-18:03:09 (Codemgr)
 *   SunPro Code Manager data about conflicts, renames, etc...
 *   Name history : 2 1 server/Request.java
 *   Name history : 1 0 Request.java
 *
 * 1.1 98/09/14-18:03:08 (suhler)
 *   date and time created 98/09/14 18:03:08 by suhler
 *
 */

/* MODIFICATIONS
 * 
 * Modifications to this file are derived, directly or indirectly, from Original Code provided by the
 * Initial Developer and are Copyright 2010-2011 Google Inc.
 * See Changes.txt for a complete list of changes from the original source code.
 */

package sunlabs.brazil.server;

import sunlabs.brazil.properties.PropertiesList;
import sunlabs.brazil.util.http.HttpInputStream;
import sunlabs.brazil.util.http.HttpUtil;
import sunlabs.brazil.util.http.MimeHeaders;
import sunlabs.brazil.util.http.UnchunkingInputStream;

import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;

/**
 * Represents an HTTP transaction.   A new instance is created
 * by the server for each connection.
 * <p>
 * Provides a set of accessor functions to fetch the individual fields
 * of the HTTP request.
 * <p>
 * Utility methods that are generically useful for manipulating HTTP
 * requests are included here as well.  An instance of this class is
 * passed to handlers.  There will be exactly one request object per thead
 * at any time.
 * <p>
 * The fields
 * {@link #headers}, 
 * {@link #query}, and
 * {@link #url}, and the method
 * {@link #getQueryData()}
 * are most often used to examine the content of the request.
 * The field
 * {@link #props}
 * contains information about the server, or up-stream handlers.
 * <p>
 * The methods
 * {@link #sendResponse(String, String, int)} and
 * {@link Request#sendError(int, String)}
 * are commonly used to return content to the client.  The methods
 * {@link #addHeader(String)} and
 * {@link #setStatus(int)} can be used to modify the response headers
 * and return code respectively before the response is sent.
 * <p>
 * Many of the other methods are used internally, but can be useful to
 * handlers that need finer control over the output that the above methods
 * provide.  Note that the order of the methods is important.  For instance,
 * the user cannot change the HTTP response headers (by calling the
 * <code>addHeader</code> method or by modifying the
 * <code>responseHeaders</code> field) after having already sent an HTTP
 * response.
 * <p>
 * A number of the fields in the <code>Request</code> object are public,
 * by design.  Many of the methods are convenience methods; the underlying
 * data fields are meant to be accessed for more complicated operations,
 * such as changing the URL or deleting HTTP response headers.  
 *
 * @see		Handler
 * @see		Server
 *
 * @author	Stephen Uhler (stephen.uhler@sun.com)
 * @author	Colin Stevens (colin.stevens@sun.com)
 * @version		2.18
 */
public class Request
{
  /**
   * Maximum number of blank lines allowed between requests before
   * aborting the connecion.  The spec allows 0, but some clients add
   * one or more.
   */    
  //request.getProps().put("context_path", req.getContextPath());

  public static final int MAX_BLANKS = 10;
  /**
   * The server that initiated this request.  Only under rare circumstances
   * should this be modified.
   */
  public Server server;
  /**
   * Our connection to the client. Only under rare circumstances would this
   * need to be modified.
   */
  protected HttpInputStream in;

  /**
   * A set of properties local to this request.  The property is wrapped
   * in a <code>PropertiesList</code> object and initially is the head
   * of a linked list of properties that are searched in order.
   * This is useful for handlers that wish to communicate via properties
   * to down-stream handlers, such as modifying a server property for a
   * particular request.  Some handlers may even add entire new sets of
   * properties onto the front of <code>request.props</code> to temporarily
   * modify the properties seen by downstream handlers. 
   */
  private PropertiesList props;

  /**
   * A <code>PropertiesList</code> object that wraps
   * <code>server.props</code>.  When this <code>request</code> is
   * created, a new <code>PropertiesList</code>, IOException wrapping 
   * <code>server.props</code> is created and added to a list consisting
   * only of <code>props</code> and <code>serverProps</code>.
   */
  private PropertiesList serverProps;

  /**
   * The HTTP response to the client is written to this stream.  Normally
   * the convenience methods, such as <code>sendResponse</code>, are used
   * to send the response, but this field is available if a handler
   * needs to generate the response specially.
   * <p>
   * If the user chooses to write the response directly to this stream, the
   * user is still encouraged to use the convenience methods, such as
   * <code>sendHeaders</code>, to first send the HTTP response headers.
   * The {@link sunlabs.brazil.filter.FilterHandler}
   * examines the HTTP response headers
   * set by the convenience methods to determine whether to filter the
   * output.  
   * <p>
   * Note that the HTTP response headers will <b>not</b> automatically be
   * sent as a side effect if the user writes to this stream.  The user
   * would either need to call the convenience method
   * <code>sendHeaders</code> or need to generate the HTTP response headers
   * themselves.
   * <p>
   * This variable is declared as a <code>Request.HttpOutputStream</code>,
   * which provides the convenience method <code>writeBytes</code> to write
   * the byte representation of a string back to the client.  If the user
   * does not need this functionality, this variable may be accessed
   * simply as a normal <code>OutputStream</code>.
   *
   * @see	#sendResponse(String, String, int)
   * @see	#sendHeaders(int, String, int)
   */
  public HttpOutputStream out;

  /*
   * How many requests this <code>Request</code> will handle.  If this goes
   * to 0, then the connection will be closed even if
   * <code>keepAlive</code> is true.
   */
  protected int requestsLeft;


  //-----------------------------------------------------------------------

  /**
   * The HTTP request method, such as "GET", "POST", or "PUT".
   */
  private String method;

  /**
   * The URL specified in the request, not including any "?" query
   * string.
   * <p>NOTE: Traditionally handlers modify this as needed.  The request
   * property "url.orig" is set to match the url at creation time, and
   * should be considered "Read only", for those cases where the original
   * url is required.
   */
  private String url;

  /**
   * The query string specified after the URL, or <code>""</code> if no
   * query string was specified.
   */
  private String query;

  /**
   * The HTTP protocol specified in the request, either "HTTP/1.0" or
   * "HTTP/1.1".
   *
   * @see	#version
   */
  private String protocolVersion;

  /**
   * Derived from {@link #protocolVersion}, the version of the HTTP protocol
   * used for this request.  Either <code>10</code> for "HTTP/1.0" or
   * <code>11</code> for "HTTP/1.1".
   */
  private int version;

  /**
   * The HTTP request headers.  Keys and values in this table correspond
   * the field names and values from each line in the HTTP header;
   * field names are case-insensitive, but the case of the values is
   * preserved.  The order of entries in this table corresponds to the
   * order in which the request headers were seen.  Multiple header lines
   * with the same key are stored as separate entries in the table.
   */
  private MimeHeaders headers;

  /**
   * The uploaded content of this request, usually from a POST.  Set to
   * <code>null</code> if the request has no content.
   */
  private byte[] postData;

  /**
   * <code>true</code> if the client requested a persistent connection,
   * <code>false</code> otherwise.  Derived from the {@link #protocolVersion} and
   * the {@link #headers},
   * <p>
   * When "Keep-Alive" is requested, the client can issue multiple,
   * consecutive requests via a single socket connection.  By default: <ul>
   * <li> HTTP/1.0 requests are not Keep-Alive, unless the
   * "Connection: Keep-Alive" header was present.
   * <li> HTTP/1.1 requests are Keep-Alive, unless the "Connection: close"
   * header was present.
   * </ul>
   * The user can change this value from <code>true</code> to
   * <code>false</code> to forcefully close the connection to the client
   * after sending the response.  The user can change this value from
   * <code>false</code> to <code>true</code> if the client is using a
   * different header to request a persistent connection.  See
   * {@link #connectionHeader}.
   * <p>
   * Regardless of this value, if an error is detected while receiving
   * or responding to an HTTP request, the connection will be closed.
   */
  private boolean keepAlive;

  /**
   * The header "Connection" usually controls whether the client
   * connection will be of type "Keep-Alive" or "close".  The same
   * header is written back to the client in the response headers.
   * <p>
   * The field {@link #keepAlive} is set based on the value of the
   * "Connection" header.  However, not all clients use "Connection"
   * to request that the connection be kept alive.  For instance (although
   * it does not appear in the HTTP/1.0 or HTTP/1.1 documentation) both
   * Netscape and IE use the "Proxy-Connection" header when issuing
   * requests via an HTTP proxy.  If a <code>Handler</code> is written to
   * respond to HTTP proxy requests, it should set <code>keepAlive</code>
   * depending on the value of the "Proxy-Connection" header, and set
   * <code>connectionHeader</code> to "Proxy-Connection", since the
   * convenience methods like <code>setResponse()</code> use these fields
   * when constructing the response.  The server does not handle the
   * "Proxy-Connection" header by default, since trying to pre-anticipate
   * all the exceptions to the specification is a "slippery slope".
   */
  private String connectionHeader;

  /**
   * This is the server's protocol.  It is normally null, but
   * may be overriden to change the protocol on a per-request
   * basis.  If not set then server.protocol should be used instead.
   */

  private String serverProtocol;

  //-----------------------------------------------------------------------

  protected int statusCode;
  protected String statusPhrase;

  /**
   * The HTTP response headers.  Keys and values in this table correspond
   * to the HTTP headers that will be written back to the client when
   * the response is sent.  The order of entries in this table corresponds
   * to the order in which the HTTP headers will be sent.  Multiple header
   * lines with the same key will be stored as separate entries in the
   * table.
   *
   * @see	#addHeader(String, String)
   */
  private MimeHeaders responseHeaders;

  /*
   * True if the headers have already been sent, so that if sendError()
   * is called in the middle of sending a response, it won't send the
   * headers again, but will cause the connection to be closed afterwards.
   */
  protected boolean headersSent;

  /**
   * Time stamp for start of this request - set, but not used.
   */
  private long startMillis;

  /**
   *
   * If true, 0 length responses are turned into "204 no content".
   * Setting this violates the spec, but is expected behaviour in
   * many cases.
   */
  private static boolean noContentAs204 = false;

  /**
   * Size of the input/output buffers on the socket streams.
   * '0' uses the system default size.
   */
  public static int bufSize = 0;

  /**
   * Create a new http request.  Requests are created by the server for
   * use by handlers.
   *
   * @param	server
   *		The server that owns this request.
   *
   */
  public Request(Server server,InputStream in, OutputStream out) {
    this.server = server;
    this.in =  new HttpInputStream(in);
    if (out instanceof Request.HttpOutputStream) {
      this.out=(Request.HttpOutputStream) out;
    } else {
      this.out = new Request.HttpOutputStream(out);
    }


    requestsLeft = server.maxRequests;
    setKeepAlive(true);

    setHeaders(new MimeHeaders());
    setResponseHeaders(new MimeHeaders());
    setServerProtocol(null);
  }

  /**
   * Needed by VelocityFilter.Vrequest.  Should not be used to create
   * a <code>Request</code> object.
   */
  protected Request() {}

  /**
   * Returns a string representation of this <code>Request</code>.
   * The string representation is the first line (the method line) of the
   * HTTP request that this <code>Request</code> is handling.  Useful for
   * debugging.
   *
   * @return	The string representation of this <code>Request</code>.
   */
  @Override
  public String
  toString()
  {
    StringBuffer sb = new StringBuffer();

    sb.append(getMethod()).append(' ').append(getUrl());
    if ((getQuery() != null) && (getQuery().length() > 0)) {
      sb.append('?').append(getQuery());
    }
    sb.append(' ').append(getProtocol());
    return sb.toString();
  }

  /**
   * Reads an HTTP request from the socket.
   *
   * @return	<code>true</code> if the request was successfully read and
   *		parsed, <code>false</code> if the request was malformed.
   *
   * @throws	IOException
   *		if there was an IOException reading from the socket.  See
   *		the socket documentation for a description of socket
   *		exceptions.
   */
  public boolean
  getRequest()
  throws IOException
  {
    if (server.getProps().get("debugProps") != null) {
      if (getProps() != null) {
        getProps().dump(false, "at beginning of getRequest");
      }
    }

    /*
     * Reset state.
     */

    requestsLeft--;
    setConnectionHeader("Connection");

    setMethod(null);
    setUrl(null);
    setQuery(null);
    setProtocolVersion("HTTP/1.1");
    getHeaders().clear();
    setPostData(null);

    statusCode = 200;
    statusPhrase = "OK";
    getResponseHeaders().clear();
    setStartMillis(System.currentTimeMillis());
    out.bytesWritten=0;

    setServerProtocol(null);

    /*
     * Get first line of HTTP request (the method line).
     */

    String line = null;
    int count = 0;
    while (count++ < MAX_BLANKS) {
      line = in.readLine(MimeHeaders.MAX_LINE);
      if (line == null) {
        return false;
      } else if (line.length() > 0) {
        break;
      }
      log(Server.LOG_INFORMATIONAL, "Skipping blank line");
    }

    if (count >= MAX_BLANKS) {
      throw new IOException("Too many leading blanks in HTTP request");
    }

    log(Server.LOG_LOG, "Request " + requestsLeft + " " + line);

    try {
      StringTokenizer st = new StringTokenizer(line);
      setMethod(st.nextToken());
      setUrl(st.nextToken());
      setProtocolVersion(st.nextToken());
    } catch (NoSuchElementException e) {
      sendError(400, line, null);
      return false;
    }

    if (getProtocolVersion().equals("HTTP/1.0")) {
      setVersion(10);
    } else if (getProtocolVersion().equals("HTTP/1.1")) {
      setVersion(11);
    } else {
      sendError(505, line, null);
      return false;
    }

    /*
     * Separate query string from URL.
     */

    int index = getUrl().indexOf('?');
    if (index >= 0) {
      setQuery(getUrl().substring(index + 1));
      setUrl(getUrl().substring(0, index));
    } else {
      setQuery("");
    }

    getHeaders().read(in);

    /*
     * Remember POST data.  "Transfer-Encoding: chunked" is not handled
     * yet.
     */

    String str;

    str = getRequestHeader("Content-Length");
    if (str != null) {
      int len;
      try {
        len=Integer.parseInt(str);
        if (len > server.maxPost) {
          log(Server.LOG_DIAGNOSTIC, "Request", "too much post data");
          sendError(413, len + " bytes is too much data to post", null);
          return false;
        }
        setPostData(new byte[len]);
      } catch (Exception e) {
        sendError(411, str, null);
        return false;
      } catch (OutOfMemoryError e) {
        log(Server.LOG_DIAGNOSTIC, "Request", "out of memory for post data");
        sendError(413, str, null);
        return false;
      }
      log(Server.LOG_DIAGNOSTIC, "Request", "Reading content: " + str);
      in.readFully(getPostData());

      // dechunk input

    } else if ((str = getRequestHeader("Transfer-Encoding")) != null &&
        str.equals("chunked")) {
      ByteArrayOutputStream bout = new ByteArrayOutputStream();
      byte[] buf = new byte[4096];
      UnchunkingInputStream unchunk = new UnchunkingInputStream(in,
          getHeaders());
      int len;
      int total=0;
      while((len = unchunk.read(buf, 0, buf.length)) > 0) {
        bout.write(buf, 0, len);
        total += len;
        if (total > server.maxPost) {
          log(Server.LOG_DIAGNOSTIC, "Request", "too much post data");
          sendError(413, total + " bytes is too much data to post", null);
          return false;
        }
      }
      setPostData(bout.toByteArray());
    }

    str = getRequestHeader(getConnectionHeader());
    if ("Keep-Alive".equalsIgnoreCase(str)) {
      setKeepAlive(true);
    } else if ("close".equalsIgnoreCase(str)) {
      setKeepAlive(false);
    } else if (getVersion() > 10) {
      setKeepAlive(true);
    } else {
      setKeepAlive(false);
    }

    /*
     * Delay initialization until we know we need these things
     */
    setServerProps(new PropertiesList(server.getProps()));
    setProps(new PropertiesList());
    getProps().addBefore(getServerProps());

    /*
     * Keep track of the original url.  This is backwards (for
     * historical reasons
     */

    getProps().put("url.orig", getUrl());

    return true;
  }

  boolean
  shouldKeepAlive()
  {
    return (requestsLeft > 0) && isKeepAlive();
  }

  /**
   * The socket from which the HTTP request was received, and to where the
   * HTTP response will be written.  The user should not directly read from
   * or write to this socket.  The socket is provided other purposes, for
   * example, imagine a handler that provided different content depending
   * upon the IP address of the client.
   *
   * @return	The client socket that issued this HTTP request.
   */

  /**
   * Logs a message by calling <code>Server.log</code>.  Typically a
   * message is generated on the console or in a log file, if the
   * <code>level</code> is less than the current server log setting. 
   *
   * @param	level
   *		The severity of the message.  
   *
   * @param	message
   *		The message that will be logged.
   *
   * @see	Server#log(int, Object, String)
   */
  public void
  log(int level, String message)
  {
    log(level, null, message);
  }

  /**
   * Logs a message by calling <code>Server.log</code>.  Typically a
   * message is generated on the console or in a log file, if the
   * <code>level</code> is less than the current server log setting. 
   *
   * @param	level
   *		The severity of the message.  
   *
   * @param	obj
   *		The object that the message relates to.
   *
   * @param	message
   *		The message that will be logged.
   *
   * @see	Server#log(int, Object, String)
   */
  public void
  log(int level, Object obj, String message)
  {
    server.log(level, obj, message);
  }

  /*
   *-----------------------------------------------------------------------
   * Request methods.
   *-----------------------------------------------------------------------
   */

  /**
   * Returns the value that the given case-insensitive key maps to
   * in the HTTP request headers.  In order to do fancier things like
   * changing or deleting an existing request header, the user may directly
   * access the <code>headers</code> field.
   *
   * @param	key
   *		The key to look for in the HTTP request headers.  May not
   *		be <code>null</code>.
   *
   * @return	The value to which the given key is mapped, or
   *		<code>null</code> if the key is not in the headers.
   *
   * @see	#headers
   */
  public String
  getRequestHeader(String key)
  {
    return getHeaders().get(key);
  }

  /**
   * Retrieves the query data as a hashtable.
   * This includes both the query information included as part of the url 
   * and any posted "application/x-www-form-urlencoded" data.
   *
   * @param   table
   *		An existing hashtable in which to put the query data as
   *		name/value pairs.  May be <code>null</code>, in which case
   *		a new hashtable is allocated.
   *
   * @return	The hashtable in which the query data was stored.
   */
  public Hashtable<String, String>
  getQueryData(Hashtable<String, String> table)
  {
    if (table == null) {
      table = new Hashtable<String, String>();
    }
    HttpUtil.extractQuery(getQuery(), table);
    if (getPostData() != null) {
      String contentType = getHeaders().get("Content-Type");
      if (contentType != null &&
          contentType.indexOf("www-form-urlencoded") != -1) {
        HttpUtil.extractQuery(new String(getPostData()), table);
      }
    }
    return table;
  }

  /**
   * Retrieves the query data as a hashtable.
   * This includes both the query information included as part of the url 
   * and any posted "application/x-www-form-urlencoded" data.
   *
   * @return	The hashtable in which the query data was stored.
   */
  public Hashtable<String, String>
  getQueryData()
  {
    return getQueryData(null);
  }

  /*
   *-----------------------------------------------------------------------
   * Response methods.
   *-----------------------------------------------------------------------
   */

  /**
   * Sets the status code of the HTTP response.  The default status
   * code for a response is <code>200</code> if this method is not
   * called.
   * <p>
   * An HTTP status phrase will be chosen based on the given
   * status code.  For example, the status code <code>404</code> will get
   * the status phrase "Not Found".
   * <p>
   * If this method is called, it must be called before
   * <code>sendHeaders</code> is either directly or indirectly called.
   * Otherwise, it will have no effect.
   *
   * @param	code
   *		The HTTP status code, such as <code>200</code> or
   *		<code>404</code>.  If &lt; 0, the HTTP status code will
   *		not be changed.
   *
   * @see	#sendHeaders(int, String, int)
   */
  public void
  setStatus(int code)
  {
    if (code >= 0) {
      setStatus(code, HttpUtil.getStatusPhrase(code));
    } 
  }

  /**
   * Set the HTTP status code and status phrase of this request.  The given
   * status will be sent to the client when the user directly or indirectly
   * calls the method <code>sendHeaders</code>.  The given status phrase
   * replaces the default HTTP status phrase normally associated with the
   * given status code.
   *
   * @param	code
   *		The HTTP status code, such as <code>200</code> or
   *		<code>404</code>.
   *
   * @param	message
   *		The HTTP status phrase, such as <code>"Okey dokey"</code> or
   *		<code>"I don't see it"</code>.
   *
   * @see	#sendHeaders(int, String, int)
   */
  protected void
  setStatus(int code, String message)
  {
    this.statusCode = code;
    this.statusPhrase = message;
  }

  /**
   * Return the status code.
   */

  public int getStatus() {
    return statusCode;
  }

  /**
   * Return uses of this socket
   */

  public int getReuseCount() {
    return server.maxRequests - requestsLeft;
  }

  /**
   * Adds a response header to the HTTP response.  In order to do fancier
   * things like appending a value to an existing response header, the
   * user may directly access the <code>responseHeaders</code> field.
   * <p>
   * If this method is called, it must be called before
   * <code>sendHeaders</code> is either directly or indirectly called.
   * Otherwise, it will have no effect.
   *
   * @param	key
   *		The header name.  
   *
   * @param	value
   *		The value for the request header.
   *
   * @see	#sendHeaders(int, String, int)
   * @see	#responseHeaders
   */
  public void
  addHeader(String key, String value)
  {
    getResponseHeaders().add(key, value);
  }

  /**
   * Adds a response header to the HTTP response.  In order to do fancier
   * things like appending a value to an existing response header, the
   * user may directly access the <code>responseHeaders</code> field.
   * <p>
   * If this method is called, it must be called before
   * <code>sendHeaders</code> is either directly or indirectly called.
   * Otherwise, it will have no effect.
   *
   * @param	line
   *		The HTTP response header, of the form
   *		"<code>key</code>: <code>value</code>".
   *
   * @see	#sendHeaders(int, String, int)
   * @see	#responseHeaders
   */
  public void
  addHeader(String line)
  {
    int dots = line.indexOf(':');
    String key = line.substring(0, dots);
    String value = line.substring(dots + 1).trim();
    addHeader(key, value);
  }
  public void
  replaceHeader(String key, String value)
  {
    getResponseHeaders().put(key, value);
  }

  /**
   * Sends an HTTP response to the client.  
   * <p>
   * This method first calls <code>sendHeaders</code> to send the HTTP
   * response headers, then sends the given byte array as the HTTP
   * response body. If the request method is HEAD, or the result
   * code is "204", the body is not sent.
   * <p>
   * The "Content-Length" will be set to the length of the given byte array.
   * The "Content-Type" will be set to the given MIME type.
   *
   * @param	body
   *		The array of bytes to send as the HTTP response body.  May
   *		not be <code>null</code>.
   *
   * @param	type
   *		The MIME type of the response, such as "text/html".  May be
   *		<code>null</code> to use the existing "Content-Type"
   *		response header (if any).
   *
   * @throws	IOException
   *		if there was an I/O error while sending the response to
   *		the client.
   *
   * @see	#sendHeaders(int, String, int)
   */
  public void
  sendResponse(byte[] body, String type)
  throws IOException
  {
    if (statusCode == 204) {
      sendHeaders(-1, type, 0);
    } else {
      sendHeaders(-1, type, body.length);
      if (!getMethod().equals("HEAD")) {
        out.write(body);
      }
    }
  }

  /**
   * Sends an HTTP response to the client.  
   * <p>
   * This method first calls <code>sendHeaders</code> to send the HTTP
   * response headers.  It then writes out the given string to the client
   * as a sequence of bytes.  Each character in the string is written out
   * by discarding its high eight bits.
   * <p>
   * The "Content-Length" will be set to the length of the string.
   * The "Content-Type" will be set to the given MIME type.
   * <p>
   * Note: to use a different character encoding, use
   * <code>sendResponse(body.getBytes(encoding)...)</code> instead.
   *
   * @param	body
   *		The string to send as the HTTP response body.  May
   *		not be <code>null</code>. If the request method is HEAD,
   *          the body is not sent.
   *
   * @param	type
   *		The MIME type of the response, such as "text/html".  May be
   *		<code>null</code> to preserve the existing "Content-Type"
   *		response header (if any).
   *
   * @param	code
   *		The HTTP status code for the response, such as
   *		<code>200</code>.  May be &lt; 0 to preserve the existing
   *		status code.
   *
   * @throws	IOException
   *		if there was an I/O error while sending the response to
   *		the client. 
   *
   * @see	#sendHeaders(int, String, int)
   */
  public void
  sendResponse(String body, String type, int code)
  throws IOException
  {
    if (statusCode == 204) {
      sendHeaders(-1, type, 0);
    } else {
      sendHeaders(code, type, body.length());
      if (!"HEAD".equals(getMethod())) {
        out.writeBytes(body);
      }
    }
  }

  /**
   * Convenience method that sends an HTTP response to the client
   * with a "Content-Type" of "text/html" and the default HTTP status
   * code.
   *
   * @param	body
   *		The string to send as the HTTP response body.
   *
   * @see	#sendResponse(String, String, int)
   */
  public void
  sendResponse(String body)
  throws IOException
  {
    sendResponse(body, "text/html", -1);
  }

  /**
   * Convenience method that sends an HTTP response to the client
   * with the default HTTP status code.
   * 
   * @param	body
   *		The string to send as the HTTP response body.  
   *		If the request method is HEAD,
   *          only the headers are sent to the client.
   *
   * @param	type
   *		The MIME type of the response.
   *
   * @see	#sendResponse(String, String, int)
   */
  public void
  sendResponse(String body, String type)
  throws IOException
  {
    sendResponse(body, type, -1);
  }

  /**
   * Sends the contents of the given input stream as the HTTP response.
   * <p>
   * This method first calls <code>sendHeaders</code> to send the HTTP
   * response headers.  It then transfers a total of <code>length</code>
   * bytes of data from the given input stream to the client as the
   * HTTP response body.
   * <p>
   * This method takes care of setting the "Content-Length" header
   * if the actual content length is known, or the "Transfer-Encoding"
   * header if the content length is not known (for HTTP/1.1 clients only).
   * <p>
   * This method may set the <code>keepAlive</code> to <code>false</code>
   * before returning, if fewer than <code>length</code> bytes could be
   * read. If the request method is HEAD, only the headers are sent.
   *
   * @param	in
   *		The input stream to read from.  
   *
   * @param	length
   *		The content length.  The number of bytes to send to the
   *		client.  May be &lt; 0, in which case this method will read
   *		until reaching the end of the input stream.
   * 
   * @param	type
   *		The MIME type of the response, such as "text/html".  May be
   *		<code>null</code> to preserve the existing "Content-Type"
   *		response header (if any).
   *
   * @param	code
   *		The HTTP status code for the response, such as
   *		<code>200</code>.  May be &lt; 0 to preserve the existing
   *		status code.
   *
   * @throws	IOException
   *		if there was an I/O error while sending the response to
   *		the client. 
   */
  public void
  sendResponse(InputStream in, int length, String type, int code)
  throws IOException
  {
    HttpInputStream hin = new HttpInputStream(in);

    byte[] buf = new byte[server.getBufsize()];

    if (length >= 0) {
      sendHeaders(code, type, length);
      if (!getMethod().equals("HEAD")) {
        if (hin.copyTo(out, length, buf) != length) {
          setKeepAlive(false);
        }
      }
    } else if (getVersion() <= 10) {
      setKeepAlive(false);
      sendHeaders(code, type, -1);
      if (!getMethod().equals("HEAD")) {
        hin.copyTo(out, -1, buf);
      }
    } else {
      if (getMethod().equals("HEAD")) {
        sendHeaders(code, type, -1);
        return;
      }

      /* Typically we won't know weather to chunk or not until
       * after we "send" the headers, so we set "chunked" encoding, 
       * and let it be removed downstream (Yuk!)
       */

      addHeader("Transfer-Encoding", "chunked");
      sendHeaders(code, type, -1);

      if (out.canChunk()) {
        while (true) {
          int count = hin.read(buf);
          if (count < 0) {
            out.writeBytes("0\r\n\r\n");
            break;
          }
          out.writeBytes(Integer.toHexString(count) + "\r\n");
          out.write(buf, 0, count);
          out.writeBytes("\r\n");
        }
      } else {
        int count;
        while ((count = hin.read(buf)) >= 0) {
          out.write(buf, 0, count);
        }
      }
    }
  }

  /**
   * Sends a HTTP error response to the client.  
   *
   * @param	code
   *		The HTTP status code.
   *
   * @param	clientMessage
   *		A short message to be included in the error response
   *		and logged to the server.
   */
  public void
  sendError(int code, String clientMessage)
  {
    sendError(code, clientMessage, null);
  }

  /**
   * Sends a HTTP error response to the client.  
   *
   * @param	code
   *		The HTTP status code.
   *
   * @param	clientMessage
   *		A short message to be included in the error response.
   *
   * @param	logMessage
   *		A short message to be logged to the server.  This message is
   *		<b>not</b> sent to the client.
   */
  public void
  sendError(int code, String clientMessage, String logMessage)
  {
    setStatus(code);
    server.incrErrorCount();

    String message = clientMessage;
    if (message == null) {
      message = logMessage;
      logMessage = null;
    }
    log(Server.LOG_LOG, "Error",
        statusCode + " " + statusPhrase + ": " + message);
    if (logMessage != null) {
      log(Server.LOG_LOG, logMessage);
    }

    setKeepAlive(false);
    if (headersSent) {
      /*
       * The headers have already been sent.  We can't send an error
       * message in the middle of an existing response, so just close
       * this request.
       */

      return;
    }

    String body = "<html>\n<head>\n"
      + "<title>Error: " + statusCode + "</title>\n"
      + "<body>\nGot the error: <b>"
      + statusPhrase
      + "</b><br>\nwhile trying to obtain <b>"
      + ((getUrl() == null) ? "unknown URL" : HttpUtil.htmlEncode(getUrl()))
      + "</b><br>\n"
      + HttpUtil.htmlEncode(clientMessage)
      + "\n</body>\n</html>";

    try {
      sendResponse(body, "text/html", statusCode);
    } catch (IOException e) {
      /*
       * Don't throw an error in the process of sending an error
       * message!
       */
    }
  }

  /**
   * Sends the HTTP status line and response headers to the client.  This
   * method is automatically invoked by <code>sendResponse</code>, but
   * can be manually invoked if the user needs direct access to the
   * client's output stream.  If this method is not called, then the
   * HTTP status and response headers will not automatically be sent to
   * the client; the user would be responsible for forming the entire
   * HTTP response.
   * <p>
   * The user may call the <code>addHeader</code> method or modify the
   * <code>responseHeaders</code> field before calling this method.
   * This method then adds a number of HTTP headers, as follows: <ul>
   * <li> "Date" - the current time, if this header is not already present.
   * <li> "Server" - the server's name (from <code>server.name</code>), if
   *	    this header is not already present.
   * <li> "Connection" - "Keep-Alive" or "close", depending upon the
   *	    <code>keepAlive</code> field.
   * <li> "Content-Length" - set to the given <code>length</code>.
   * <li> "Content-Type" - set to the given <code>type</code>.
   * </ul>
   * <p>
   * The string used for "Connection" header actually comes from the
   * <code>connectionHeader</code> field.
   *
   * @param	code
   *		The HTTP status code for the response, such as
   *		<code>200</code>.  May be &lt; 0 to preserve the existing
   *		status code.
   *
   * @param	type
   *		The MIME type of the response, such as "text/html".  May be
   *		<code>null</code> to preserve the existing "Content-Type"
   *		response header (if any).
   *
   * @param	length
   *		The length of the response body.  May be &lt; 0 if the length
   *		is unknown and/or to preserve the existing "Content-Length"
   *		response header (if any).
   *
   * @throws	IOException
   *		if there was an I/O error while sending the headers to
   *		the client. 
   *
   * @see	#setStatus(int)
   * @see	#addHeader(String, String)
   * @see	#sendResponse(String, String, int)
   * @see	#connectionHeader
   */
  public void
  sendHeaders(int code, String type, int length)
  throws IOException
  {
    setStatus(code);
    if (isNoContentAs204() && (length == 0) && (statusCode == 200)) {
      /*
       * No Content.
       */
      setStatus(204);
    }

    getResponseHeaders().putIfNotPresent("Date", HttpUtil.formatTime());
    if (server.getNameStr() != null) {
      getResponseHeaders().putIfNotPresent("Server", server.getNameStr());
    }
    String str = shouldKeepAlive() ? "Keep-Alive" : "close";
    getResponseHeaders().put(getConnectionHeader(), str);
    if (length >= 0) {
      getResponseHeaders().put("Content-Length", Integer.toString(length));
      getResponseHeaders().remove("Transfer-Encoding");
    }
    if (type != null) {
      getResponseHeaders().putIfNotPresent("Content-Type", type);
    }

    out.sendHeaders(this);
    // System.out.println("*** Sending headers: " +url+ " " + responseHeaders);
    headersSent = true;
  }

  /**
   * Send the response headers to the client.
   *  This consists of standard plus added headers.  The handler is reponsible
   *  for sending the reponse body.
   *
   * @param type	The document mime type
   * @param length	the document length
   * 
   * @see Request#addHeader(String)
   * @see Request#sendResponse(String)
   * @see Request#setStatus(int)
   */


  /**
   * Responds to an HTTP request with a redirection reply, telling the
   * client that the requested url has moved.  Generally, this is used if
   * the client did not put a '/' on the end of a directory.
   *
   * @param	url
   *		The URL the client should have requested.  This URL may be
   *		fully-qualified (in the form "http://....") or host-relative
   *		(in the form "/...").
   *
   * @param	body
   *		The body of the redirect response, or <code>null</code> to
   *		send a hardcoded message.
   */
  public void
  redirect(String url, String body)
  throws IOException
  {
    if (url.startsWith("/")) {
      url = serverUrl() + url;
    }
    addHeader("Location", url);
    if (body == null) {
      body = "<title>Moved</title><h1>look for <a href=" +
      url + ">" + url + "</h1>";
    }
    sendResponse(body, "text/html", 302);
  }

  /**
   * Returns the server's fully-qualified base URL.  This is "http://"
   * followed by the server's hostname and port.
   * <p>
   * If the HTTP request header "Host" is present, it specifies the
   * hostname and port that will be used instead of the server's internal
   * name for itself.  Due bugs in certain browsers, when using the server's
   * internal name, the port number will be elided if it is <code>80</code>.
   * (or 443 for https)..
   *
   * @return	The string representation of the server's URL.
   */
  public String
  serverUrl()
  {
    String host = getHeaders().get("Host");
    String protocol = getProtocol();
    int dfltPort =  protocol.equals("http") ? 80 : 443;
    if (host == null) {
      host = server.getHostName();
    }
    int index = host.lastIndexOf(":");
    if ((index < 0) && (server.getPort() != dfltPort)) {
      host += ":" + server.getPort();
    }
    return protocol + "://" + host;
  }

  /**
   * Return the protocol for this request.  Usually "http"
   */
  public String getProtocol() 
  {
    return getServerProtocol()==null?server.getProtocol():getServerProtocol();
  }

  /**
   * The <code>HttpOutputStream</code> provides the convenience method
   * <code>writeBytes</code> for writing the byte representation of a
   * string, without bringing in the overhead and the deprecated warnings
   * associated with a <code>java.io.DataOutputStream</code>.
   * <p>
   * The other methods in this class are here to allow the 
   * <code>FilterHandler</code> and <code>ChainSawHandler</code> to
   * alter the behavior in an implementation specific way.  This behavior
   * is unfortunate, and might go away when a better strategy comes along.
   */
  public static class HttpOutputStream
  extends FilterOutputStream
  {
    /**
     * Count the number of bytes that are written to this stream
     */

    public int bytesWritten = 0;

    public
    HttpOutputStream(OutputStream out)
    {
      super(out);
    }

    public void
    writeBytes(String s)
    throws IOException
    {
      int len = s.length();
      for (int i = 0; i < len; i++) {
        this.out.write((byte) s.charAt(i));
      }
      bytesWritten += len;
    }

    public void write(byte b) throws IOException {
      this.out.write(b);
      bytesWritten++;
    }

    @Override
    public void
    write(byte[] buf, int off, int len) throws IOException {
      this.out.write(buf, off, len);
      bytesWritten += len;
    }

    public void
    sendHeaders(Request request)
    throws IOException
    {
      writeBytes(request.getProtocol() + " " + request.statusCode + " " +
          request.statusPhrase + "\r\n");
          request.getResponseHeaders().print(this.out);
          writeBytes("\r\n");
    }

    /**
     * By default, http/1.1 output streams support chunking.
     * If further output processing is required, then the chunking gets
     * in the way. Allow my subclasses to turn chunking off.  The
     * downstream processing functions need to insure valid http/1.1
     * in than case.
     */

    public boolean canChunk() {
      return true;
    }
  }

  /**
   * Adds the given <code>Dictionary</code> to the set of properties that
   * are searched by <code>request.props.getProperty()</code>.  This method
   * is used to optimize the case when the caller has an existing
   * <code>Dictionary</code> object that should be added to the search
   * chain.
   * <p>
   * Assume the caller is constructing a new <code>Properties</code>
   * object and wants to chain it onto the front of
   * <code>request.props</code>.  The following code is appropriate:
   * <code><pre>
   * /&#42; Push a temporary Dictionary onto request.props. &#42;/
   * PropertiesList old = request.props;
   * (new PropertiesList()).addBefore(request.props);
   * request.props = request.props.getPrior();
   * request.props.put("foo", "bar");
   * request.props.put("baz", "garply");
   *
   * /&#42; Do something that accesses new properties. &#42;/
   *     .
   *     .
   *     .
   *
   * /&#42; Restore old Dictionary when done. &#42;/
   * request.props.remove();
   * request.props = old;
   * </pre></code>
   * However, <code>addSharedProps</code> may be called when the caller
   * has an existing set of <code>Properties</code> and is faced with
   * copying its contents into <code>request.props</code> and/or trying
   * to share the existing <code>Properties</code> object among multiple
   * threads concurrently.
   * <code><pre>
   * /&#42; Some properties created at startup. &#42;/
   * static Properties P = new Properties();
   *     .
   *     .
   *     .
   * /&#42; Share properties at runtime. &#42;/
   * request.addSharedProps(P);
   * </pre></code>is more efficient and esthetically pleasing than:
   * <code><pre>
   * foreach key in P.keys() {
   *     request.props.put(key, P.get(key));
   * }
   * </pre></code>
   * The given <code>Dictionary</code> object is added to the
   * <code>Properties.getProperty()</code> search chain before serverProps;
   * it will be searched after the 
   * <code>request.props</code> and before <code>serverProps</code>.
   * Multiple <code>Dictionary</code> objects can be added and they will
   * be searched in the order given.  The same <code>Dictionary</code>
   * object can be added multiple times safely.  However, the search
   * chain for the given <code>Dictionary</code> must not refer back to
   * <code>request.props</code> itself or a circular chain will be
   * created causing an infinite loop:
   * <code><pre>
   * request.addSharedProps(request.props);	            // Bad
   * request.addSharedProps(request.props.getWrapped());  // Good
   * Properties d1 = new Properties(request.props);
   * request.addSharedProps(d1);                          // Bad
   * Hashtable d2 = new Hashtable();
   * Properties d3 = new Properties();
   * request.addSharedProps(d2);		            // Good
   * request.addSharedProps(d3);		            // Good
   * </pre></code>
   * Subsequent calls to <code>request.props.getProperty()</code> may
   * fetch properties from an added <code>Dictionary</code>, but
   * <code>request.put()</code> will <b>not</b> modify those dictionaries.
   *
   * @param	d
   *		A <code>Dictionary</code> of <code>String</code> key/value
   *		pairs that will be added to the chain searched
   *		when <code>request.props.getProperty()</code> is called.  The
   *		dictionary <code>d</code> is "live", meaning that external
   *		changes to the contents of <code>d</code> will be seen on
   *		subsequent calls to <code>request.props.getProperty()</code>.
   *
   * @return	<code>false</code> if the dictionary had already been added
   *		by a previous call to this method, <code>true</code>
   *		otherwise.
   */
  public boolean addSharedProps(Dictionary d) {
    return addSharedProps(d, null);
  }

  /**
   * Add a dictionary to the search list with an enumeration 
   * prefix.  This reflects how the dictionary will be enumerated
   * with "glob" - enumeration will be skipped entirely if the
   * glob pattern doesn't start with the prefix.
   */
  public boolean
  addSharedProps(Dictionary d, String prefix)
  {
    boolean debug = server.getProps().get("debugProps") != null;
    if (getProps().wraps(d) != null) {
      if (debug) {
        System.out.println("addSharedProps didn't add dict"
            + Integer.toHexString(System.identityHashCode(d)));
      }
      return false;
    }
    PropertiesList pl = new PropertiesList(d);
    pl.addAfter(getProps());
    if (debug) {
      getProps().dump(true, "at addSharedProps");
    }
    pl.setPrefix(prefix);
    return true;
  }

  /**
   * Add a dictionary with the specified enumeration prefix
   */

  /**
   * Removes a <code>Dictionary</code> added by
   * <code>addSharedProps</code>.  <code>Dictionary</code> objects may
   * be removed in any order.  <code>Dictionary</code> objects do not need
   * to be removed; they will automatically get cleaned up at the end of
   * the request.
   *
   * @param	d
   *		The <code>Dictionary</code> object to remove from the
   *		<code>request.props.getProperty()</code> search chain.
   *
   * @return	<code>true</code> if the <code>Dictionary</code> was found
   *		and removed, <code>false</code> if the <code>Dictionary</code>
   *		was not found (it had already been removed or had never been
   *		added).
   */
  public boolean
  removeSharedProps(Dictionary d)
  {
    PropertiesList pl = getProps().wraps(d);
    if (pl != null && pl != getProps() && pl != getServerProps()) {
      pl.remove();
      if (server.getProps().get("debugProps") != null) {
        pl.dump(true, "at removeSharedProps");
      }
      return true;
    }
    return false;
  }

  public void setProps(PropertiesList props) {
    this.props = props;
  }

  public PropertiesList getProps() {
    return props;
  }

  public void setServerProps(PropertiesList serverProps) {
    this.serverProps = serverProps;
  }

  public PropertiesList getServerProps() {
    return serverProps;
  }

  public void setMethod(String method) {
    this.method = method;
  }

  public String getMethod() {
    return method;
  }

  public void setUrl(String url) {
    this.url = url;
  }

  public String getUrl() {
    return url;
  }

  public void setQuery(String query) {
    this.query = query;
  }

  public String getQuery() {
    return query;
  }

  public void setProtocolVersion(String protocol) {
    this.protocolVersion = protocol;
  }
  
  public String getProtocolVersion() {
    return protocolVersion;
  }

  public void setVersion(int version) {
    this.version = version;
  }

  public int getVersion() {
    return version;
  }

  public void setHeaders(MimeHeaders headers) {
    this.headers = headers;
  }

  public MimeHeaders getHeaders() {
    return headers;
  }

  public void setPostData(byte[] postData) {
    this.postData = postData;
  }

  public byte[] getPostData() {
    return postData;
  }

  public void setKeepAlive(boolean keepAlive) {
    this.keepAlive = keepAlive;
  }

  public boolean isKeepAlive() {
    return keepAlive;
  }

  /**
   * @param connectionHeader the connectionHeader to set
   */
   public void setConnectionHeader(String connectionHeader) {
     this.connectionHeader = connectionHeader;
   }

   /**
    * @return the connectionHeader
    */
   public String getConnectionHeader() {
     return connectionHeader;
   }

   /**
    * @param serverProtocol the serverProtocol to set
    */
   public void setServerProtocol(String serverProtocol) {
     this.serverProtocol = serverProtocol;
   }

   /**
    * @return the serverProtocol
    */
   public String getServerProtocol() {
     return serverProtocol;
   }

   /**
    * @param responseHeaders the responseHeaders to set
    */
   public void setResponseHeaders(MimeHeaders responseHeaders) {
     this.responseHeaders = responseHeaders;
   }

   /**
    * @return the responseHeaders
    */
   public MimeHeaders getResponseHeaders() {
     return responseHeaders;
   }

   /**
    * @param startMillis the startMillis to set
    */
   public void setStartMillis(long startMillis) {
     this.startMillis = startMillis;
   }

   /**
    * @return the startMillis
    */
   public long getStartMillis() {
     return startMillis;
   }

   /**
    * @param noContentAs204 the noContentAs204 to set
    */
   public static void setNoContentAs204(boolean noContentAs204) {
     Request.noContentAs204 = noContentAs204;
   }

   /**
    * @return the noContentAs204
    */
   public static boolean isNoContentAs204() {
     return noContentAs204;
   }
}
